
using System.Collections;
using System.Runtime.InteropServices;
using KeraLua;

namespace Ex
{
    public class ExCsData<T> : ExCsRawData where T : class
    {

        protected struct Field
        {
            public Func<T, object?>? Getter;
            public Action<T, object>? Setter;
        }

        public delegate void AddListenerHandler<EventType>(T obj, Action<EventType> listener);
        public delegate void AddListenerHandler(T obj, Action<object[]> listener);

        protected delegate void RawAddListenerHandler(T obj, EnvLua env);

        protected readonly static Type _class = typeof(T) ?? throw new InvalidDataException("unexpected type");

        protected static Hashtable _fields = new();

        protected static Hashtable _field_funcs = new();

        protected static Dictionary<string, RawAddListenerHandler> _field_event_add_handles = new();

        public const string LabelFuncUnRef = "Unref";

        public const string LabelFuncAddListener = "AddEventListener";


        public T SpecificData
        {
            get
            {
                if (Data is T data)
                    return data;
                throw new Exception("Invalid type");
            }
        }

        public ExCsData(EnvLua lua, T data) : base(lua, data, _class.GUID.ToString())
        {
            InitClassMetatable();
        }

        protected static int CsobjectAddEventListener(nint status, EnvLua env)
        {
            var lua = env.Current;
            var ptr = lua.ToUserData(1);
            try
            {
                if (CheckValid(ptr, out GCHandle? gc_ptr) is ExCsRawData data && gc_ptr != null)
                {
                    if (!lua.IsString(2))
                    {
                        env.Error("param at 1 should be luaString");
                    }
                    var event_name = lua.ToString(2);
                    if (!_field_event_add_handles.ContainsKey(event_name))
                    {
                        env.Error($"invalid event name: {event_name}");
                    }
                    var handler = _field_event_add_handles[event_name];
                    handler(DataCast(data).SpecificData, env);
                    return 0;
                }
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                lua.Pop(2);
            }
            env.Error("param at 0 should be csobject");
            return 0;
        }


        public new static bool IsType(ExCsRawData data)
        {
            return data.IsType(_class);
        }

        /// <summary>
        /// 类型转换
        /// </summary>
        /// <param name="rawData"></param>
        /// <returns></returns>
        /// <exception cref="InvalidCastException"></exception>
        public static ExCsData<T> DataCast(ExCsRawData rawData)
        {
            if (rawData.Data is T data)
                return new ExCsData<T>(rawData.CurrentLua, data);
            rawData.CurrentLua.Error($"can not cast {rawData.Data} to {typeof(T)}");
            throw new Exception();
        }

        public static ExCsData<ChildType>.AddListenerHandler<EType> HandlerCast<EType, ChildType>(AddListenerHandler<EType> handler) where ChildType : class, T
        {
            return (e, listener) => handler(e, listener);
        }

        public virtual void InitClassMetatable()
        {
            InitMetatable(env);
        }

        /// <summary>
        /// 类型<typeparamref name="T"/>检测是否已经在lua的metatable注册信息，
        /// <br/>
        /// 如果没有，依据现有静态设置注册该类的成员变量方法及静态方法
        /// </summary>
        /// <param name="env"></param>
        /// <remarks>
        /// thread unsafe
        /// </remarks>
        /// <exception cref="InvalidOperationException"></exception>
        public static void InitMetatable(EnvLua env)
        {
            var lua = env.Current;
            if (_class_table.ContainsKey(_class.GUID.ToString()))
            {
                return;
            }
            //注册metatable
            if (!lua.NewMetaTable(_class.GUID.ToString()))
            {
                lua.Pop(1);
                throw new Exception("unexpcted error: conflict metatable name");
            }
            lua.PushString("__tostring");
            lua.PushCFunction(state => OriginToString(env, state));
            lua.SetTable(-3);
            lua.PushString("__index");
            lua.PushCFunction(state => OriginIndex(env, state));
            lua.SetTable(-3);
            lua.PushString("__newindex");
            lua.PushCFunction(state => OriginNewIndex(env, state));
            lua.SetTable(-3);
            lua.PushString("__gc");
            lua.PushCFunction(state => LuaUnregister(lua, state));
            lua.SetTable(-3);
            lua.Pop(1);
            _class_table.AddOrUpdate(
                _class.GUID.ToString(),
                _class,
                (key, value) => value
            );
        }

        protected static int OriginToString(EnvLua env, nint status)
        {
            lock (env)
            {
                env.Current.Pop(1);
                env.Current.PushString(_class.FullName);
                return 1;
            }
        }

        protected static int OriginIndex(EnvLua env, nint status)
        {
            lock (env)
            {
                var param = IndefiniteVarType.FetchFromLuaStack(env, new UsableLuaType[] { UsableLuaType.csobject, UsableLuaType.luaString });
                if (param.GetData(0) is ExCsRawData raw && ExCsData<T>.DataCast(raw) is ExCsData<T> data)
                {
                    if (param.GetString(1) is string key)
                    {
                        var lua = env.Current;
                        if (key == LabelFuncUnRef)
                        {
                            lua.PushCFunction(state => LuaUnregister(lua, state));
                            return 1;
                        }
                        else if (key == LabelFuncAddListener)
                        {
                            lua.PushCFunction(state => CsobjectAddEventListener(state, env));
                            return 1;
                        }
                        else if (_fields.ContainsKey(key))
                        {
                            var getter = ((Field)_fields[key]!).Getter;
                            if (getter is null)
                            {
                                env.Error("forbid to get field:" + key);
                                return 0;
                            }
                            else if (getter(data.SpecificData) is object value)
                            {
                                env.PushObjectToLuaStack(value);
                                return 1;
                            }
                            else
                            {
                                return 0;
                            }
                        }
                        else if (_field_funcs.ContainsKey(key))
                        {
                            var func = (ExCsClassFunction)_field_funcs[key]!;
                            env.PushObjectToLuaStack(func);
                            return 1;
                        }
                    }
                    return 0;
                }
                throw new Exception("unexpcted error: failed to get itself");
            }
        }

        protected static int OriginNewIndex(EnvLua env, nint status)
        {
            lock (env)
            {
                var lua = env.Current;
                var param = IndefiniteVarType.FetchFromLuaStack(env, 3);
                if (param.GetData(0) is ExCsRawData raw && ExCsData<T>.DataCast(raw) is ExCsData<T> data)
                {
                    if (param.GetString(1) is string key && _fields.ContainsKey(key))
                    {
                        var setter = ((Field)_fields[key]!).Setter;
                        if (setter is null)
                        {
                            env.Error("forbid to set field:" + key);
                            return 0;
                        }
                        else
                        {
                            setter(data.SpecificData, param.Get(2));
                        }
                        return 1;
                    }
                    else
                    {
                        env.Error("Invalid operation");
                        return 0;
                    }
                }
                else
                    throw new Exception("unexpcted error: failed to get itself");
            }
        }

        /// <summary>
        ///为类添加属性
        /// <br/>
        ///所注入的属性对于所有同泛型类生效
        /// </summary>
        public static void AddField(string name, Func<T, object?>? getter = null, Action<T, object>? setter = null, UsableLuaType type = UsableLuaType.any)
        {
            if (type != UsableLuaType.any)
            {
                _fields.Add(name, new Field()
                {
                    Getter = getter,
                    Setter = (t, obj) =>
                    {
                        setter?.Invoke(t, obj);
                    }
                });
            }
            else
            {
                _fields.Add(name, new Field() { Getter = getter, Setter = setter });
            }
        }

        public static void AddField(string name, Action<T, object> setter, UsableLuaType type = UsableLuaType.any)
        {
            AddField(name, null, setter, type);
        }

        public static void AddFieldFunc(string name, ExCsClassFunction func)
        {
            _field_funcs.Add(name, func);
        }

        public static void AddFieldEvent<EventType>(string name, AddListenerHandler<EventType> add) where EventType : class
        {
            _field_event_add_handles.Add(
                name, 
                (RawAddListenerHandler)((obj, env) =>
                {
                    if (!env.Current.IsFunction(-1))
                    {
                        env.Error("param at 2 should be luaFunction");
                    }
                    var event_listener = (IExLuaFunction)env.FetchObjectFromLuaStack(UsableLuaType.function);
                    add(
                        obj,
                        @event =>
                        {
                            event_listener.Call(0, new ExCsData<EventType>(env, @event));
                        }
                    );
                })
            );
        }
        public static void AddFieldEvent(string name, AddListenerHandler add)
        {
            _field_event_add_handles.Add(
                name, 
                (obj, env) =>
                {
                    if (!env.Current.IsFunction(-1))
                    {
                        env.Error("param at 2 should be luaFunction");
                    }
                    var event_listener = (IExLuaFunction)env.FetchObjectFromLuaStack(UsableLuaType.function);
                    add(
                        obj,
                        param => { event_listener.CallFromArray(0, param); }
                    );
                }
            );
        }

    }
}